package com.ybear.ybutils.utils.log;

import static com.ybear.ybutils.utils.time.DateTimeType.DAY;
import static com.ybear.ybutils.utils.time.DateTimeType.HOUR;
import static com.ybear.ybutils.utils.time.DateTimeType.MINUTE;
import static com.ybear.ybutils.utils.time.DateTimeType.MONTH;
import static com.ybear.ybutils.utils.time.DateTimeType.SECOND;
import static com.ybear.ybutils.utils.time.DateTimeType.YEAR;

import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import androidx.arch.core.util.Function;

import com.alibaba.fastjson.JSON;
import com.ybear.ybutils.utils.IOUtil;
import com.ybear.ybutils.utils.SysUtil;
import com.ybear.ybutils.utils.handler.ThreadPool;
import com.ybear.ybutils.utils.time.DateTime;

import java.io.File;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 日志类
 */
public class LogUtil {
    private static final String TAG = "YB_TAG";
    private static final String[] STRING_LOG_TYPE = { "Verbose", "Debug", "Info", "Warn", "Error" };
    private static final LogEntity ENTITY = new LogEntity();

    private static String mTagOfGlobal = null;
    private static boolean isDebug = false;
    private static boolean isCall = false;

    private static Function<LogEntity, LogEntity> mCallLog;

    private static boolean isSkipLogPrintJson = true;
    private static final Set<Class<?>> mLogPrintJsonClassSet = new HashSet<>();

    /**
     保存日志时的编码
     @param charset     编码
     */
    public static void setLogSaveCharset(@NonNull Charset charset) {
        ENTITY.setCharset( charset );
    }

    /* 日志文件保存路径（仅日志路径，不包括文件名） */
    public static void setLogSavePath(@NonNull File savePath, boolean enable) {
        ENTITY.setSavePath( savePath, enable );
    }
    public static void setLogSavePath(@NonNull File filePath) {
        setLogSavePath( filePath, true );
    }
    public static void setLogSavePath(String savePath, boolean enable) {
        setLogSavePath( new File( savePath ), enable );
    }
    public static void setLogSavePath(String savePath) {
        setLogSavePath( savePath, true );
    }
    public static void setLogSavePath(Application app, boolean enable) {
        //path:/storage/emulated/0/Android/data/[your package name]/cache/logs/
        // or
        //path:/storage/emulated/0/Android/obb/[your package name]/logs/
        File file = null;
        try {
            file = app.getExternalCacheDir();
        } catch (Exception e) {
            e.printStackTrace();
        }
         try {
             if( file == null ) file = SysUtil.getObbDir( app );
        } catch (Exception e) {
            e.printStackTrace();
        }
        if( file == null ) return;
        setLogSavePath(
                file.getAbsolutePath() + File.separator + "logs" + File.separator,
                enable
        );
    }
    public static void setLogSavePath(Application app) { setLogSavePath( app, true ); }

    public static File getLogSaveFile() { return ENTITY.getSavePath(); }

    public static String getLogSavePath() { return getLogSaveFile().getAbsolutePath(); }

    public static boolean isEnableSaveLog() { return ENTITY.isEnableSave(); }

    /**
     设置生成新日志文件的间隔（时间越长，日志数量越少，单个日志文件存储空间越大）
     @param second      秒（默认值：{@link LogEntity#setNewLogFileIntervalSecond(long)}）
     */
    public static void setNewLogFileIntervalSecond(long second) {
        ENTITY.setNewLogFileIntervalSecond( second );
    }

    /**
     获取生成新日志文件的间隔
     @return        秒
     */
    public static long getNewLogFileIntervalSecond() { return ENTITY.getNewLogFileIntervalSecond(); }

    /**
     设置保存日志的类型文本（保存日志时会携带该文本）
     @param types       new String[] { "V", "D", "I", "W", "E" };
     */
    public static void setLogSaveTypeText(@Size( 5 ) String[] types) {
        if( types == null || types.length < 5 ) return;
        System.arraycopy( types, 0, STRING_LOG_TYPE, 0, STRING_LOG_TYPE.length );
    }

    /**
     * 设置全局打印的tag
     * @param tag   tag
     */
    public static void setTagOfGlobal(@NonNull String tag) { mTagOfGlobal = tag; }

    /**
     * 是否处于调试模式
     * @return      结果
     */
    public static boolean isDebug() { return isDebug; }

//    /**
//     * 编译时，是否为调试模式
//     * @return      根据编译类型返回，release=false，bate=true， debug=true
//     */
//    public static boolean isBuildDebug() { return BuildConfig.DEBUG; }

//    /**
//     * 编译类型
//     * @return      根据编译类型返回，release=Release，bate=Bate， debug=Debug
//     */
//    public static String getBuildTypeString() { return BuildConfig.BUILD_TYPE_STRING; }

//    /**
//     * 编译类型
//     * @return      根据编译类型返回，release=2，bate=1， debug=0
//     */
//    public static int getBuildType() { return BuildConfig.BUILD_TYPE; }

    /**
     * 允许启用输出日志功能
     * @param enable        正式环境（release）下不会输出日志（false）。
     *                      除非你在manifest的application中配置：android:debuggable="true"
     * @param isAuto        无视<code>enable</code>的说明
     */
    public static void setDebugEnable(@NonNull Application app, boolean enable, boolean isAuto) {
        if( isAuto ) {
            int flag = ApplicationInfo.FLAG_DEBUGGABLE;
            //第二个判断意味着传入true时会验证当前环境是否也处于调试模式，非调试模式时为false
            isDebug = enable && ( app.getApplicationInfo().flags & flag ) == flag;
        } else {
            isDebug = enable;
        }
        setLogSavePath( app );
    }

    public static void setDebugEnable(@NonNull Application app, boolean enable) {
        setDebugEnable( app, enable, false );
    }

    public static void setDebugEnableOfAuto(@NonNull Application app) {
        setDebugEnable( app, true, true );
    }

    /**
     * 是否允许启用日志保存功能
     * @param enable    是否启用
     */
    public static void setCallLogEnable(boolean enable) {
        isCall = enable;
    }

    /**
     * 启用日志后会回调日志接口，保存操作交付给开发者自行保存  @param call  回调

     */
    public static void setOnCallLogListener(Function<LogEntity, LogEntity> call) {
        mCallLog = call;
    }

    /**
     * 跳过将实体类输出为json（默认跳过）
     */
    public static void setSkipLogPrintJson(boolean skipLogPrintJson) {
        isSkipLogPrintJson = skipLogPrintJson;
    }

    /**
     * 添加被标记过的class会被输出为json
     */
    public static void addLogPrintJsonClass(Class<?> cls) {
        mLogPrintJsonClassSet.add( cls );
    }
    public static void addLogPrintJsonClass(Class<?>... cls) {
        mLogPrintJsonClassSet.addAll( Arrays.asList( cls ) );
    }

    /**
     * 移除被标记过的class会被输出为json
     */
    public static void removeLogPrintJsonClass(Class<?> cls) {
        mLogPrintJsonClassSet.remove( cls );
    }
    /**
     * 清空全部被标记过的class会被输出为json
     */
    public static void clearLogPrintJsonClass() {
        mLogPrintJsonClassSet.clear();
    }

    public static String toJSONString(Object obj) {
        return JSON.toJSONString( obj );
    }

    /****** 泛型 tag *******/
    public static <T,S> int v(T tag, @Nullable S msg, Object... args) {
        return printLog( LogType.V, isSkipLogPrintJson, tag, msg, args );
    }
    public static <T,S> int d(T tag, @Nullable S msg, Object... args) {
        return printLog( LogType.D, isSkipLogPrintJson, tag, msg, args );
    }
    public static <T,S> int i(T tag, @Nullable S msg, Object... args) {
        return printLog( LogType.I, isSkipLogPrintJson, tag, msg, args );
    }
    public static <T,S> int w(T tag, @Nullable S msg, Object... args) {
        return printLog( LogType.W, isSkipLogPrintJson, tag, msg, args );
    }
    public static <T,S> int e(T tag, @Nullable S msg, Object... args) {
        return printLog( LogType.E, isSkipLogPrintJson, tag, msg, args );
    }

    /****** Default tag *******/
    public static <S> int v(S msg) { return v( null, msg ); }
    public static <S> int d(S msg) { return d( null, msg ); }
    public static <S> int i(S msg) { return i( null, msg ); }
    public static <S> int w(S msg) { return w( null, msg ); }
    public static <S> int e(S msg) { return e( null, msg ); }

    /****** Default tag with args *******/
    public static <S> int v(S msg, Object[] args) { return v( null, isSkipLogPrintJson, msg, args ); }
    public static <S> int d(S msg, Object[] args) { return d( null, isSkipLogPrintJson, msg, args ); }
    public static <S> int i(S msg, Object[] args) { return i( null, isSkipLogPrintJson, msg, args ); }
    public static <S> int w(S msg, Object[] args) { return w( null, isSkipLogPrintJson, msg, args ); }
    public static <S> int e(S msg, Object[] args) { return e( null, isSkipLogPrintJson, msg, args ); }

    /**
     根据类型打印日志
     @param type        日志类型
     @param gTag        日志标签
     @param gMessage    日志内容
     @param args        format参数
     @param <T>         标签泛型
     @param <S>         内容泛型
     @return            返回结果
     */
    private static <T,S> int printLog(@LogType int type, boolean skipPrintJson, T gTag, S gMessage, Object... args) {
        String tag = toTag( gTag );
        String msg = toMessage( gMessage );

        tag = ( mTagOfGlobal == null ? TAG : mTagOfGlobal ) +
                ( "null".equalsIgnoreCase( tag.trim() ) ? "" : "_" + tag );

        if( msg == null || msg.length() == 0 ) msg = "";
        //追加参数
        try {
            if( args != null && args.length > 0 ) {
                //json输出检查
                if( !skipPrintJson ) {
                    for (int i = 0; i < args.length; i++) {
                        Object arg = args[ i ];
                        if( arg == null ) continue;
                        boolean find = false;
                        //第一种模式是实现 ILogPrintJson 接口
                        if( arg instanceof ILogPrintJson ) {
                            find = true;
                        }else {
                            //第二种模式是加入到set列表中
                            for( Class<?> cls : mLogPrintJsonClassSet ) {
                                if( !arg.equals( cls ) ) continue;
                                find = true;
                                break;
                            }
                        }
                        if( find ) args[ i ] = toJSONString( arg );
                    }
                }
                msg = String.format( msg, args );
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //是否回调日志
        if( isCall ) callLog( tag, type, msg );
        //是否打印日志
        if( !isDebug ) return -1;

        int index = 0;
        int maxLen = 4000;          //logcat单次最大字节容量
        int msgLen = msg.length();
        int ret = 0;
        String subMsg;
        while( index < msgLen ) {
            //分割日志
            subMsg = msg.substring( index, Math.min( msgLen, index + maxLen ) );
            //输出日志
            ret = printTypeLog( tag, subMsg, type );
            index += maxLen;
        }
        return ret;
    }

    private static int printTypeLog(@NonNull String tag, @NonNull String msg, @LogType int type) {
        if ( type == LogType.D ) return Log.d( tag, msg );
        if ( type == LogType.I ) return Log.i( tag, msg );
        if ( type == LogType.W ) return Log.w( tag, msg );
        if ( type == LogType.E ) return Log.e( tag, msg );
        return Log.v( tag, msg );
    }

//    private static final CopyOnWriteArrayList<String> currentCallLogListFiles = new CopyOnWriteArrayList<>();

    private static String lastSaveLogName = null;
    /**
     * 保存日志
     * @param msg   日志内容
     * @param type  日志类型
     */
    private static void callLog(@NonNull String tag, @LogType int type, @NonNull String msg) {
        if( mCallLog == null ) return;
        ThreadPool.get().execute(() -> {
            long currentTimeMillis = DateTime.currentTimeMillis();
//            String previousPath = ENTITY.getSavePath().getAbsolutePath();
            ENTITY.setTag( tag )
                    .setType( type )
                    .setTypeString( STRING_LOG_TYPE[ type ] )
                    .setMessage( msg )
                    .setCreateTimestamp( currentTimeMillis );
            LogEntity entity = mCallLog.apply( ENTITY );
            //不保存日志到本地
            if( !entity.isEnableSave() ) return;
            //保存路径
            File saveFile = entity.getSavePath();
            String path = saveFile.getPath();
            //如果启用日志保存功能必须设置日志路径
            if( TextUtils.isEmpty( path ) ) {
                throw new NullPointerException("If the log saving function is enabled, you must set the log path!");
            }
            String curName = null;
            //写到日志的格式（customMsg 不为空时，直接写入customMsg，否则按照既定格式保存）
            try {
                String data = TextUtils.isEmpty( entity.getCustomMessage() ) ?
                        String.format( "%s [%s/%s]\n%s\n",
                                DateTime.parse( currentTimeMillis ),
                                entity.getTypeString(),
                                entity.getTag(),
                                entity.getMessage()
                        ) :
                        entity.getCustomMessage();
                //保存日志文件
                IOUtil.saveFile(
                        path,
                        curName = getLogName( lastSaveLogName ),
                        data.getBytes( entity.getCharset() ),
                        curName.equals( lastSaveLogName )
                );
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lastSaveLogName = curName;
            }
        });
    }

    /**
     获取日志名
     @param lastName    上一个日志名。如果时效还在则原样返回，否则创建一个新的名称
     @return            日志名
     */
    @NonNull
    private static String getLogName(@Nullable String lastName) {
        String prefix = "log_";
        String postfix = ".log";
        long lastTs = -1;
        //获取上个日志的时间（eg:20211019143008698）
        if( lastName != null && lastName.startsWith( prefix ) && lastName.endsWith( postfix ) ) {
            //时间转时间戳（eg:1634625008701）
            try {
                lastTs = DateTime.parse(
                        lastName.substring( prefix.length(), lastName.length() - postfix.length() ),
                        YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
                );
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        long saveInterval = ENTITY.getNewLogFileIntervalSecond() * 1000;
        //时间不存在 或者 超出时间区间，重新创建新的日志名
        if( lastTs == -1 || System.currentTimeMillis() - lastTs >= saveInterval ) {
            return prefix + DateTime.nows( YEAR, MONTH, DAY, HOUR, MINUTE, SECOND ) + postfix;
        }
        return lastName;
    }

    @NonNull
    private static <T> String toTag(@Nullable T tag) {
        if( tag == null || tag instanceof String ) return String.valueOf( tag );
        if( tag instanceof Class ) return ((Class<?>)tag).getSimpleName();
        return tag.getClass().getSimpleName();
    }

    @Nullable
    private static <T> String toMessage(@Nullable T msg) {
        if( msg == null ) return null;
        if( msg instanceof Throwable ) {
            Throwable t = (Throwable) msg;
            return t.getMessage() + "\n" + Arrays.toString( t.getStackTrace() );
        }
        return String.valueOf( msg );
    }
}